﻿namespace EyeOpen.Validation.Mapping
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// Allows to validate an entity and its properties.
    /// </summary>
    /// <typeparam name="T">The type of entity.</typeparam>
    public abstract class EntityValidator<T> : IEntityValidator<T> where T : class 
    {
        private readonly ValidationMapping<T> validationMapping;

        private readonly IList<ValidationResult> validationResults;

        protected EntityValidator()
        {
            validationResults = new List<ValidationResult>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EntityValidator&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="validationMapping">The validation mapping.</param>
        protected EntityValidator(ValidationMapping<T> validationMapping)
        {
            if (validationMapping == null)
            {
                throw new ArgumentNullException("validationMapping");
            }

            this.validationMapping = validationMapping;
            validationResults = new List<ValidationResult>();
        }

        /// <summary>
        /// Gets or sets the entity validation messages.
        /// </summary>
        /// <value>The entity validation messages.</value>
        public IEnumerable<ValidationResult> ValidationResults
        {
            get
            {
                return validationResults;
            }
        }

        /// <summary>
        /// Determines whether the specified entity is valid.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns>
        /// <c>true</c> if the specified entity is valid; otherwise, <c>false</c>.
        /// </returns>
        public bool IsValid(T obj)
        {
            ClearValidationResults();

            if (validationMapping != null)
            {
                foreach (var propertyMapped in validationMapping.PropertiesMapped)
                {
                    var propertyValue =
                        typeof(T)
                            .GetProperty(propertyMapped.PropertyName, BindingFlags.Instance | BindingFlags.Public)
                            .GetValue(obj, null);

                    var validationMessages = new List<string>();

                    foreach (var validator in propertyMapped.Validators)
                    {
                        if (!validator.IsValid(propertyValue))
                        {
                            validationMessages.Add(validator.ValidationMessage);
                        }
                    }

                    if (validationMessages.Count > 0)
                    {
                        var validationResult =
                            new ValidationResult
                                {
                                    PropertyName = propertyMapped.PropertyName,
                                };

                        foreach (var validationMessage in validationMessages)
                        {
                            validationResult.ValidationMessages.Add(validationMessage);
                        }

                        AddValidationResult(validationResult);
                    }
                }
            }

            ValidateEntity(obj);

            var entityValidation = ValidationResults == null || ValidationResults.Count() == 0;
            
            return entityValidation;
        }

        /// <summary>
        /// Validates the entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        protected virtual void ValidateEntity(T entity)
        {
        }

        protected void AddValidationResult(ValidationResult validationResult)
        {
            validationResults.Add(validationResult);
        }

        protected void ClearValidationResults()
        {
            validationResults.Clear();
        }
    }
}